select 的語法與 switch 很相似,但與 switch 不同的是,select 中雖然也是使用 case,但在 select 中的 case 必须都是 Channel 的收發操作。可以參考下圖片,來自 參考來源2,裡面有介紹大量的原始碼,有興趣可以看看,然後下面有個簡單例子:
func generator() chan int {
out := make(chan int)
go func() {
i := 0
for {
out <- i
i++
}
}()
return out
}
func main() {
var c1, c2 = generator(), generator()
for {
select {
case n := <-c1:
fmt.Println("Received from c1:", n)
case n := <-c2:
fmt.Println("Received from c2:", n)
}
}
}
輸出結果為:
Received from c2: 30322
Received from c2: 30323
Received from c2: 30324
Received from c2: 30325
Received from c1: 27700
Received from c2: 30326
Received from c2: 30327
Received from c2: 30328
Received from c2: 30329
Received from c1: 27701
Received from c2: 30330
Received from c2: 30331
在生成的過程中可以看到 c1 與 c2 是一個一個值丟出來的 ,這是 channel 阻塞式的特性,channel 必須先收到資料,然後再丟出資料,之後才能再收下一筆資料,那假設我們想利用 channel 做一個非阻塞式的,我們在 select 的 case 中多加入一個 default 的就可以達到這個效果,程式碼如下
// func generator() chan int {
// out := make(chan int)
// go func() {
// i := 0
// for {
// out <- i
// i++
// }
// }()
// return out
// }
// func main() {
// var c1, c2 = generator(), generator()
// for {
// select {
// case n := <-c1:
// fmt.Println("Received from c1:", n)
// case n := <-c2:
// fmt.Println("Received from c2:", n)
default:
fmt.Println("No value received")
// }
// }
// }
輸出結果為:
Received from c2: 5462
Received from c1: 5177
Received from c2: 5463
Received from c1: 5178
No value received
No value received
No value received
No value received
No value received
No value received
Received from c2: 5464
No value received
Received from c1: 5179
No value received
No value received
No value received
Received from c2: 5465
Received from c1: 5180
default 在這裡的功能就是當 channel 中的數據丟出去之後,此時 channel 中的值會變成 nil,那這時 default 就會隨著 goroutine 的速度瘋狂的輸出,也就變成了非阻塞式的結構。如果我們希望他能在搜集一定的時間後停止,我們可以在 select 裡面多加一個判斷情況,以下面例子來說,就是3秒後會停止運行
// func generator() chan int {
// out := make(chan int)
// go func() {
// i := 0
// for {
// out <- i
// i++
// }
// }()
// return out
// }
// func main() {
// var c1, c2 = generator(), generator()
tm := time.After(3 * time.Second)
// for {
// select {
// case n := <-c1:
// fmt.Println("Received from c1:", n)
// case n := <-c2:
// fmt.Println("Received from c2:", n)
case <-tm:
return
// default:
// fmt.Println("No value received")
// }
// }
// }
比方說今天有一個 10 * 10 的 array1,裡面的資料結構如下
array[0] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
array[1] = [1, 1, 2, 3, 4, 5, 6, 7, 8, 9]
array[2] = [2, 1, 2, 3, 4, 5, 6, 7, 8, 9]
然後開 10 個 channel,等到每個 channel 的資料蒐集完後
對這些資料統一都加 1,存至新的 array2 中,此時array2 的順序是混亂
這樣才能確保 goroutine 有發揮到功能
如果有大大知道怎麼做,還請不吝賜教。
https://github.com/luckyuho/ithome30-golang/tree/main/day17